package at.codebase.web.util.template;

import static at.codebase.web.util.i18n.I18NFactory.i18n;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import at.codebase.security.SecurePrintWriter;
import at.codebase.security.SecurityUtils;
import at.codebase.web.util.i18n.I18N;
import at.codebase.web.util.i18n.I18NLocale;

/**
 * Represents a single template. Use the {@link at.codebase.web.util.template.TemplateEngine} 
 * class to create a Template object of a file.
 * 
 * @author Lukas Pfeifhofer <lukas.pfeifhofer@devlabs.pro>
 *
 */
public class Template {
	
	private static final Logger log = Logger.getLogger(Template.class.getName());
	
	private enum WriterType { NONE, SECURE }
	
	private StringBuffer stringBuffer;
	private SecurePrintWriter secureWriter;
	private WriterType type;
	
	Map<String, String> replacementsText = new HashMap<String, String>();
	Map<String, String> replacementsHTML = new HashMap<String, String>();
	Map<String, Template> replacementsTemplate = new HashMap<String, Template>();
	Map<String, Template[]> replacementsTemplateCollection = new HashMap<String, Template[]>();
	
	private String renderedContent;
	private String templateContent;

	public Template(){
		this.type = WriterType.NONE;
	}

	public Template(String templateContent, SecurePrintWriter secureWriter) {
		this.templateContent = templateContent;
		this.secureWriter = secureWriter;
		this.type = WriterType.SECURE;
	}
	
	public Template(String templateContent) {
		this.templateContent = templateContent;
		this.type = WriterType.NONE;
	}
	
	public void setTemplateContent(String templateContent){
		this.templateContent = templateContent;
	}
	
	public void setTemplate(String key, Template template){
		clearKey(key);
		replacementsTemplate.put(key, template);
	}
	
	public void setTemplates(String key, ArrayList<Template> templates) {
		clearKey(key);
		replacementsTemplateCollection.put(key, templates.toArray(new Template[templates.size()]));
	}

	public void setHTML(String key, String html){
		clearKey(key);
		replacementsHTML.put(key, html);
	}
	
	public void setHTML(Map<String, String> replacements){
		Set<String> entrieKeySet = replacements.keySet();
		for(String key:entrieKeySet)
			clearKey(key);
		replacementsHTML.putAll(replacements);
	}
	
	public void setText(String key, String text){
		clearKey(key);
		replacementsText.put(key, text);
	}

	public void setText(Map<String, String> replacements){
		Set<String> entrieKeySet = replacements.keySet();
		for(String key:entrieKeySet)
			clearKey(key);
		replacementsText.putAll(replacements);
	}
	
	private void clearKey(String  key){
		if(replacementsText.containsKey(key))
			replacementsText.remove(replacementsText.get(key));
		if(replacementsHTML.containsKey(key))
			replacementsHTML.remove(replacementsHTML.get(key));
		if(replacementsTemplate.containsKey(key))
			replacementsTemplate.remove(replacementsTemplate.get(key));
		if(replacementsTemplateCollection.containsKey(key))
			replacementsTemplateCollection.remove(replacementsTemplateCollection.get(key));
	}
	
	private boolean compile(I18NLocale locale){
		/**
		 * Template Replacement
		 */
		String regexTag = "(\\{(.[^}:]+)\\})";
			
		Pattern pattern = Pattern.compile(regexTag);
		Matcher matcher = pattern.matcher(templateContent);
		
		stringBuffer = new StringBuffer();
		
		while(matcher.find()){
			
			String key = null;
			
			try {
			
			key = matcher.group(2).trim();
			
			if(replacementsHTML.containsKey(key) && replacementsHTML.get(key) != null){
				//matcher.appendReplacement(stringBuffer, Pattern.quote(replacementsHTML.get(key)));
				matcher.appendReplacement(stringBuffer, Matcher.quoteReplacement(replacementsHTML.get(key)));
			}else if(replacementsText.containsKey(key) && replacementsText.get(key) != null){
				//matcher.appendReplacement(stringBuffer, Pattern.quote(SecurityUtils.sanatize(replacementsText.get(key))));
				matcher.appendReplacement(stringBuffer, Matcher.quoteReplacement(SecurityUtils.sanatize(replacementsText.get(key))));
			}else if(replacementsTemplate.containsKey(key) && replacementsTemplate.get(key) != null){
				Template template = replacementsTemplate.get(key);
				if(!template.render(locale))
					return false;
				matcher.appendReplacement(stringBuffer, Matcher.quoteReplacement(template.getResult()));
			}else if(replacementsTemplateCollection.containsKey(key) && replacementsTemplateCollection.get(key) != null){
				Template[] templatesIn = replacementsTemplateCollection.get(key);
				StringBuffer collectionBuffer = new StringBuffer();
				for(Template templateTmp:templatesIn){
					if(!templateTmp.render(locale))
						return false;	
					collectionBuffer.append(templateTmp.getResult());
				}
				
				matcher.appendReplacement(stringBuffer, Matcher.quoteReplacement(collectionBuffer.toString()));
			}else{
				//Remove this line if non existing patterns should not be replaced.
				matcher.appendReplacement(stringBuffer, Matcher.quoteReplacement(""));
			}
			
			} catch(Exception e){
				log.severe("Error parsing: "+key);
				e.printStackTrace();
			}
		}

		matcher.appendTail(stringBuffer);
		
		
		/**
		 * I18N Replacement
		 */
		String regexTagLocale = "(\\{[\\s]*i18n:(.[^}]+)\\})";
		Pattern patternLocale = Pattern.compile(regexTagLocale);
		Matcher matcherLocale = patternLocale.matcher(stringBuffer.toString());
		
		stringBuffer = new StringBuffer();
		
		while(matcherLocale.find()){
			String matchString = matcherLocale.group(2).trim();
			
			if(!matchString.contains(":")){
				matcherLocale.appendReplacement(stringBuffer, Matcher.quoteReplacement(i18n(locale).string(matchString)));
			}else{
				String[] splittedMatchString = matchString.split(":", 2);
				String lang = splittedMatchString[0];
				String key = splittedMatchString[1];
				matcherLocale.appendReplacement(stringBuffer, Matcher.quoteReplacement(i18n(new I18NLocale(lang)).string(key)));
			}
			
		}

		matcherLocale.appendTail(stringBuffer);
		
		return true;
	}
	
	@Deprecated
	public boolean render(){
		return render(I18N.DEFAULT_LANGUAGE);
	}
	
	public boolean render(I18NLocale locale) {
		if (templateContent == null)
			return false;

		if(!compile(locale))
			return false;

		renderedContent = stringBuffer.toString();

		if (type.equals(WriterType.SECURE)) {
			if (secureWriter != null) {
				secureWriter.printInsecure(renderedContent);
			} else {
				return false;
			}
		}

		return true;
	}
	
	public String getResult(){
		return renderedContent;
	}

}
